5.22. Управляющие конструкции и операторы
Управляющие конструкции и операторы
Условные конструкции
Условные конструкции позволяют выполнять различные блоки кода в зависимости от истинности или ложности заданного выражения. В Dart основной конструкцией этого типа является if–else.
Конструкция if начинается с ключевого слова if, за которым следует выражение в круглых скобках. Если результат этого выражения приводится к логическому значению true, выполняется следующий за ним блок кода. Если значение выражения равно false, управление переходит к необязательному блоку else. Конструкция может содержать цепочку else if, что позволяет реализовать множественный выбор.
Пример:
int score = 85;
if (score >= 90) {
print('Отлично');
} else if (score >= 75) {
print('Хорошо');
} else if (score >= 60) {
print('Удовлетворительно');
} else {
print('Неудовлетворительно');
}
Dart строго типизирован и не допускает неявного приведения типов к булевому значению. Это означает, что выражение внутри if должно быть явно булевым. Попытка использовать, например, целое число в качестве условия вызовет ошибку компиляции. Такой подход повышает надежность кода и предотвращает распространенные ошибки, характерные для других языков.
Для ситуаций, где требуется выбор по значению одной переменной, Dart предлагает выражение switch. Оно работает с константными значениями и поддерживает типы данных, такие как int, String, перечисления (enum) и некоторые другие. Каждый случай выбора оформляется с помощью метки case, завершающейся обязательным оператором break, если не используется continue, return или throw. Отсутствие явного выхода из case приведет к ошибке времени компиляции — это так называемый запрет на «проваливание» (fall-through), принятый для повышения читаемости и предотвращения ошибок.
Пример:
enum Color { red, green, blue }
void describeColor(Color color) {
switch (color) {
case Color.red:
print('Цвет — красный');
break;
case Color.green:
print('Цвет — зелёный');
break;
case Color.blue:
print('Цвет — синий');
break;
}
}
Начиная с Dart 3, выражение switch стало ещё мощнее благодаря поддержке шаблонов (pattern matching), что позволяет использовать его не только для простых значений, но и для деструктуризации объектов и списков. Однако даже в классическом виде switch остается удобным инструментом для четкого и структурированного выбора.
Циклы
Циклы предназначены для многократного выполнения блока кода. Dart предоставляет три основных вида циклов: for, while и do–while.
Цикл for состоит из трех частей: инициализации, условия продолжения и шага обновления. Все эти части указываются в круглых скобках после ключевого слова for. Тело цикла выполняется до тех пор, пока условие продолжения возвращает true.
Пример:
for (int i = 0; i < 5; i++) {
print('Итерация $i');
}
Dart также поддерживает расширенную форму цикла for — так называемый цикл for-in, который используется для перебора элементов коллекций, таких как списки, множества или строки. Этот цикл автоматически извлекает каждый элемент коллекции и присваивает его переменной, объявленной в заголовке цикла.
Пример:
List<String> fruits = ['яблоко', 'банан', 'апельсин'];
for (String fruit in fruits) {
print(fruit);
}
Цикл while проверяет условие перед каждой итерацией. Если условие истинно, выполняется тело цикла. Если условие ложно с самого начала, тело цикла не выполнится ни разу.
Пример:
int count = 3;
while (count > 0) {
print('Осталось $count');
count--;
}
Цикл do–while отличается тем, что тело цикла выполняется как минимум один раз, поскольку проверка условия происходит после выполнения тела.
Пример:
int attempts = 0;
do {
print('Попытка номер ${attempts + 1}');
attempts++;
} while (attempts < 3);
Все циклы поддерживают операторы управления потоком выполнения: break для немедленного выхода из цикла и continue для перехода к следующей итерации без выполнения оставшейся части тела цикла.
Операторы сравнения и логические операторы
Операторы сравнения позволяют оценивать отношения между двумя значениями. В Dart к ним относятся:
==— равенство;!=— неравенство;<,<=,>,>=— числовые сравнения.
Результатом любого из этих операторов является булево значение (true или false). Dart строго проверяет типы при сравнении, особенно при использовании оператора ==. Для пользовательских классов поведение оператора == можно переопределить, реализовав метод operator == и соответствующий hashCode.
Логические операторы работают с булевыми значениями и включают:
&&— логическое И (возвращаетtrue, только если оба операнда истинны);||— логическое ИЛИ (возвращаетtrue, если хотя бы один операнд истинен);!— логическое НЕ (инвертирует значение:!trueдаётfalse,!false—true).
Dart поддерживает ленивые вычисления (short-circuit evaluation) для && и ||. Это означает, что если результат выражения можно определить по первому операнду, второй операнд не вычисляется. Например, в выражении a && b(), если a равно false, функция b() не будет вызвана. Это полезно для предотвращения ошибок, таких как обращение к null.
Арифметические и побитовые операторы
Арифметические операторы выполняют базовые математические действия:
+,-,*,/— сложение, вычитание, умножение, деление;~/— целочисленное деление (возвращаетint);%— остаток от деления.
Оператор / всегда возвращает значение типа double, даже если делимое и делитель — целые числа. Для получения целого результата используется ~/.
Пример:
print(7 / 2); // 3.5
print(7 ~/ 2); // 3
print(7 % 2); // 1
Побитовые операторы применяются к целым числам и работают на уровне двоичного представления:
&— побитовое И;|— побитовое ИЛИ;^— побитовое исключающее ИЛИ (XOR);~— побитовое НЕ (инверсия всех битов);<<— сдвиг влево;>>— сдвиг вправо.
Эти операторы полезны при работе с флагами, масками, низкоуровневыми протоколами или оптимизациях.
Операторы присваивания
Оператор присваивания = связывает переменную со значением. Dart также предоставляет составные операторы присваивания, которые объединяют арифметическую или побитовую операцию с присваиванием:
+=,-=,*=,/=,~/=,%=;&=,|=,^=,<<=,>>=.
Пример:
int x = 10;
x += 5; // эквивалентно x = x + 5;
Такие операторы делают код более компактным и читаемым.
Тернарный условный оператор
Тернарный оператор — это сокращённая форма условной конструкции if–else. Он имеет вид:
условие ? выражение_если_true : выражение_если_false
Оба выражения должны быть совместимы по типу, так как результат тернарного оператора должен иметь однозначный тип.
Пример:
String status = score >= 60 ? 'Сдано' : 'Не сдано';
Тернарный оператор удобен для простых условных присваиваний, но не рекомендуется для сложной логики, так как это снижает читаемость.
Операторы null-aware
Dart активно работает с концепцией null и предоставляет специальные операторы для безопасной работы с потенциально пустыми значениями:
-
??— оператор объединения с нулём (null-coalescing): возвращает левый операнд, если он неnull, иначе — правый.Пример:
String name = userName ?? 'Гость'; -
??=— присваивание только если текущее значениеnull.Пример:
config.theme ??= 'light'; -
?.— условный оператор доступа к члену: вызывает метод или обращается к свойству только если объект неnull. Если объектnull, всё выражение возвращаетnull.Пример:
int? length = user?.name?.length;
Эти операторы значительно упрощают обработку неопределённых состояний и помогают избежать ошибок времени выполнения, связанных с null.
Операторы проверки и приведения типов
Dart — язык со статической типизацией, но в некоторых случаях требуется динамическая проверка или преобразование типов. Для этого используются:
-
is— проверяет, принадлежит ли объект указанному типу. Возвращаетtrue, если тип совпадает.Пример:
if (obj is String) {
print(obj.length);
} -
is!— отрицаниеis: возвращаетtrue, если объект не принадлежит указанному типу. -
as— приведение типа. Используется, когда разработчик уверен в типе объекта, но компилятор не может это гарантировать. При несоответствии типов во время выполнения возникает исключение.Пример:
(obj as String).toUpperCase();
В большинстве случаев предпочтительнее использовать is перед обращением к свойствам, чтобы избежать необходимости в as.
Операторы управления потоком: break, continue, return
Хотя эти слова не являются «операторами» в строгом смысле, они управляют выполнением кода:
break— немедленно завершает выполнение цикла илиswitch.continue— прерывает текущую итерацию цикла и переходит к следующей.return— завершает выполнение функции и возвращает значение (если указано).
В Dart также поддерживается маркировка циклов (labeled loops), что позволяет указывать, из какого именно цикла нужно выйти при вложенных конструкциях:
outerLoop:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 1) {
break outerLoop;
}
print('($i, $j)');
}
}